package net.cyndeline

import scala.language.postfixOps
import net.cyndeline.entitySystem.EntityRef

class SlotStorage extends Slots {
  private var slots = Map[ItemType, List[Slot]]()
  
  override def size: Int = slots.values.map(_.size).sum
  
  override def size(slotType: ItemType): Int = slots.get(slotType) match {
    case Some(list) => list.size
    case None => 0
  }
  
  override def contains(item: EntityRef): Boolean = {
    for (slotList <- slots.values) {
      if (slotList.filter(x => x.item == Option(item)).size > 0) {
        return true
      }
    }
    
    false
  }
  
  override def freeSlots(slotType: ItemType): Int = if (slots contains slotType) {
    slots(slotType).filter(x => (x.item.isEmpty)).size
  } else {
    0
  }
  
  override def itemPosition(item: EntityRef, slotType: ItemType): Option[Int] = {
    val slotList = getSlots(slotType)
    for (i <- 0 until slotList.size) {
      slotList(i).item match {
        case Some(s) => if (s == item) return Option(i)
        case None => {}
      }
    }
    
    None
  }
  
  override def item(pos: Int, slotType: ItemType): Option[EntityRef] = {
    slotOutOfBoundsException(pos, slotType)
    val slotList = getSlots(slotType)
    slotList(pos).item
  }
  
  override def items(slotType: ItemType): List[(Int, Option[EntityRef])] = {
    val slotList = getSlots(slotType)
    val result = for (i <- 0 until slotList.size) yield (i, slotList(i).item)
    result.toList
  }
  
  override def addSlot(slotType: ItemType) {    
    if (slots contains slotType) {
      val slotList = slots(slotType)
      slots += (slotType -> (slotList :+ new Slot(slotList.size)))
      
    } else {
      slots += (slotType -> (Nil :+ new Slot(0)))
    }
  }
  
  override def addItem(newItem: EntityRef, pos: Int, slotType: ItemType): Boolean = {
    
    /* Only add the item if the correct slot type exists. */
    if (slots contains slotType) {
      if (isValid(pos, slotType)) {
        val slotList = slots(slotType)
        
        /* Can't store the same item in two places. */
        if (slotList.exists(_.item == Option(newItem))) {
        	return false
        }
        
        /* Only store if the selected slot is empty. */
        if (slotList(pos).item isEmpty) {
        	slotList(pos).item = Option(newItem)
        	true
        } else {
        	false
        }
        
      } else {
      throw new Error("Attempted to add a spell to a position " + pos + 
          						", but the storage size was " + slots.size + ".")
      }
    } else {
      throw new Error("Attempted to add a spell to a slot of type " + slotType + 
          						", but no such slots existed.")
    }
  }
  
  override def addItem(item: EntityRef, slotType: ItemType): Boolean = {
    if (slots contains slotType) {
      val slotList = slots(slotType)
      
      for (i <- 0 until slotList.size) {
      	if (slotList(i).item isEmpty) {
        	return addItem(item, i, slotType)
      	}
      }
      false
    } else {
      false
    }    
  }
  
  override def removeItem(remove: EntityRef): Boolean = {
    for (slotList <- slots.values) {
      for (slot <- slotList) {
        slot.item match {
	        case Some(spell) => if (spell == remove) {
	          slot.item = None
	          return true
	        }
	        case None => {}
        }
      }
    }
    
    false
  }
  
  override def moveItem(itemToMove: EntityRef, newPos: Int, toSlotType: ItemType): Boolean = {
    slotOutOfBoundsException(newPos, toSlotType)
    if (contains(itemToMove) && item(newPos, toSlotType).isEmpty) {
      removeItem(itemToMove)
      addItem(itemToMove, newPos, toSlotType)
      true
      
    } else {
      false
    }
  }
  
  override def switchItems(item1: EntityRef, item2: EntityRef, item1SlotType: ItemType, item2SlotType: ItemType): Boolean = {
    if (contains(item1) && contains(item2)) {
      val spell1Pos = itemPosition(item1, item1SlotType) get
      val spell2Pos = itemPosition(item2, item2SlotType) get
      
      removeItem(item1)
      removeItem(item2)
      addItem(item1, spell2Pos, item2SlotType)
      addItem(item2, spell1Pos, item1SlotType)
      true
      
    } else {
      false
    }
  }
  
  override def clearItems(slotType: ItemType) = {
    for (slot <- getSlots(slotType)) {
      slot.item = None
    }
  }
  
  override def clearSlots { slots = Map() }
  
  override def copy(): Slots = {
    val newSlot = new SlotStorage()
    val keyValuePairs = slots.iterator
    
    val newStore = slots.map { case (key, value) => (key, value.map(_.copy)) }
    
    for (key <- newStore.keys) {
      val values = newStore(key)
      for (value <- values) {
        newSlot.addSlot(key)
        value.item match {
          case Some(item) => newSlot.addItem(item, value.pos, key)
          case None => {}
        }
      }
    }
    newSlot
  }
  
  def addSlot(slot: Slot) {
    
  }
  
  private def isValid(slotPosition: Int, slotType: ItemType): Boolean = {
    if (slots contains slotType) {
      val slotList = getSlots(slotType)
      
      if (slotPosition >= 0 && slotPosition < slotList.size) {
        true
      } else {
        false
      }
      
    } else {
      false
    }
  }
  
  private def slotOutOfBoundsException(pos: Int, slotType: ItemType) {
    if (!isValid(pos, slotType)) {
      throw new Error("Attempted to access slot storage position " + pos + ", but storage size is " + size)
    }
  }
  
  /**
   * Inserts an element before an element that satisfies a predicate.
   */
  private def insert[A](xs: List[A], extra: List[A])(p: A => Boolean): List[A] = {
  	xs.map(x => if (p(x)) extra ::: List(x) else List(x)).flatten
  }
  
  private def getSlots(slotType: ItemType): List[Slot] = {
    slots.getOrElse(slotType, Nil)
  }
  
  private class Slot(val pos: Int) {
    var item: Option[EntityRef] = None
    
    def copy: Slot = {
      item match {
        case Some(item) => {
          val newSlot = new Slot(pos)
          newSlot.item = Option(item.copy)
          newSlot
        }
        case None => new Slot(pos)
      }
    }
  }
}